﻿using System;
using System.Text;
using System.Reflection;
using System.Ext.Xml;
using System.IO;
using System.Xml;
using Microsoft.SPOT;

namespace mfXmlSerializer
{
    /// <summary>
    /// represents the consumer usable interface for a specialized type 2 (de-)serialization
    /// </summary>
    public class mfXmlTypedSerializer : IDisposable
    {
        #region Consts
        /// <summary>
        /// default xml header (v.1.0 and utf-8)
        /// </summary>
        private const string C_STR_DEFAULT_XML_HEADER = "version=\"1.0\" encoding=\"utf-8\"";
        #endregion

        #region CTOR

        /// <summary>
        /// instantiates an new serializer
        /// creates serialization map
        /// </summary>
        /// <param name="targetType">specialized type 2 (de-)serialization</param>
        public mfXmlTypedSerializer(Type targetType)
        {
            XmlHeader = C_STR_DEFAULT_XML_HEADER;
            TargetType = targetType;

            SerializationMap = mfSerializationMapCreator.CreateMapForType(TargetType);
        }
        #endregion

        #region Properties

        /// <summary>
        /// changeable xml-header
        /// </summary>
        public String XmlHeader { get; set; }
        /// <summary>
        /// specialized type 4 (de-)serialization
        /// </summary>
        private Type TargetType { get; set; }
        /// <summary>
        /// created serialization map
        /// </summary>
        public mfSerializationMap SerializationMap { get; private set; }

        #endregion

        #region Events

        /// <summary>
        /// event to customize serialzation-process
        /// e.g.: write an attribute, like '<rss version="2.0"'/>
        /// </summary>
        public event mfDelElementSerialized ElementSerialized;

        internal static void OnElementSerialzed(mfDelElementSerialized elementSerializedAction, XmlWriter writer, string serializedElementName)
        {
            if (elementSerializedAction != null)
            {
                elementSerializedAction.Invoke(writer, serializedElementName);
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// serializes specified object as String
        /// </summary>
        /// <exception cref="mfXmlException">thrown if, problems occured on map-serialisation- or conversion-problems</exception>
        /// <param name="source">object to serialize</param>
        /// <returns>xml string</returns>
        public String Serialize(Object source)
        {
            AssertIfTypeIsIncompatible(source);

            var xmlStream = SerializeAsStream(source) as MemoryStream;
            var xmlString = xmlStream.ConvertToString();

            return xmlString;
        }

        private void AssertIfTypeIsIncompatible(Object source)
        {
            if (!TargetType.IsInstanceOfType(source))
            {
                throw new mfXmlException("target is not from type: " + TargetType.Name);
            }
        }

        /// <summary>
        /// serializes specified object as Stream
        /// </summary>
        /// <remarks>!the stream will NOT be closed (or disposed) inside!</remarks>
        /// <exception cref="mfXmlException">thrown if, problems occured on map-serialisation- or conversion-problems</exception>
        /// <param name="source">object to serialize</param>
        /// <param name="targetStream">target stream instance</param>
        public void SerializeOnStream(Object source, Stream targetStream)
        {
            AssertIfTypeIsIncompatible(source);

            XmlWriter xmlWriter = XmlWriter.Create(targetStream);

            xmlWriter.WriteProcessingInstruction("xml", XmlHeader);
            SerializationMap.SerializeMap(xmlWriter, source, ElementSerialized);
        }

        /// <summary>
        /// serializes specified object as Stream
        /// </summary>
        /// <remarks>!the stream has to be closed (or disposed) outside!</remarks>
        /// <exception cref="mfXmlException">thrown if, problems occured on map-serialisation- or conversion-problems</exception>
        /// <param name="source">object to serialize</param>
        /// <returns>serialized Stream</returns>
        public Stream SerializeAsStream(Object source)
        {
            AssertIfTypeIsIncompatible(source);

            var ms = new MemoryStream();
            XmlWriter xmlWriter = XmlWriter.Create(ms);

            xmlWriter.WriteProcessingInstruction("xml", XmlHeader);
            SerializationMap.SerializeMap(xmlWriter, source, ElementSerialized);

            return ms;
        }

        /// <summary>
        /// deserializes specified string and creates an Object
        /// conversion 4 usage is needed (as type) is recommended
        /// </summary>
        /// <exception cref="mfXmlException">thrown if, problems occured on map-deserialisation- or conversion-problems</exception>
        /// <param name="sourceString">xml string</param>
        /// <returns>created object</returns>
        public Object Deserialize(String sourceString)
        {
            var xmlStream = sourceString.ConvertToMemoryStream();

            return Deserialize(xmlStream);
        }


        /// <summary>
        /// deserializes specified stream and creates an Object
        /// conversion 4 usage is needed (as type) is recommended
        /// </summary>
        /// <exception cref="mfXmlException">thrown if, problems occured on map-deserialisation- or conversion-problems</exception>
        /// <param name="sourceStream">stream with xml content</param>
        /// <returns>created object</returns>
        public Object Deserialize(Stream sourceStream)
        {
            var readerSettings = new XmlReaderSettings() { IgnoreComments = true, IgnoreWhitespace = true, CheckCharacters = false };

            Object target = null;

            XmlReader xmlReader = XmlReader.Create(sourceStream, readerSettings);

            xmlReader.Read();//start reading
            xmlReader.Read();//skip xml header declaration or check it?!

            //instantiate new Object with empty ctor
            target = TargetType.CreateInstance(new Type[0], new object[0]);

            SerializationMap.DeserializeMap(xmlReader, target);
            
            xmlReader.Dispose();
            xmlReader.Close();

            return target;
        }

        #endregion

        #region IDisposable
        public void Dispose()
        {
            if (SerializationMap != null)
            {
                this.SerializationMap.Dispose();
                this.SerializationMap = null;
            }

            this.XmlHeader = null;
            this.TargetType = null;
        } 
        #endregion
    }
}
